package com.pai4j.pgc.service.message;
import com.pai4j.common.bean.PAIPageResponseBeanUtil;
import com.pai4j.common.constants.MessageConstants;
import com.pai4j.common.enums.RedisKeyEnum;
import com.pai4j.common.enums.community.ResourceTypeEnum;
import com.pai4j.common.util.HtmlUtil;
import com.pai4j.common.util.JsonUtil;
import com.pai4j.common.util.RedisUtil;
import com.pai4j.domain.community.vo.response.message.MessageResourceVO;
import com.pai4j.domain.community.vo.response.message.MessageResponseVO;
import com.pai4j.domain.vo.dto.BaseMessageDTO;
import com.pai4j.domain.vo.response.UserBaseResponseInfoVO;
import com.pai4j.pgc.dao.community.IMessageDAO;
import com.pai4j.pgc.entity.community.MessageEntity;
import com.pai4j.pgc.pojo.UserUnReadMessageCountVO;
import com.pai4j.pgc.service.community.CCExperienceService;
import com.pai4j.pgc.service.community.CCQuestionService;
import com.pai4j.pgc.service.community.CCVideoService;
import com.pai4j.pgc.service.resource.ResourceService;
import com.pai4j.remote.user.UserServiceClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.*;
import java.util.stream.Collectors;

/**
 *
 *
 * @author: CYM-pai
 * @date: 2025/10/31 11:13
 **/
@Slf4j
@Service
public class MessageService<T extends BaseMessageDTO> {

    @Autowired
    private IMessageDAO messageDAO;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private UserServiceClient userServiceClient;
    @Autowired
    private ResourceService resourceService;
    @Autowired
    private CCQuestionService questionService;
    @Autowired
    private CCExperienceService experienceService;
    @Autowired
    private CCVideoService videoService;

    /**
     * 发送消息
     *
     * @param data
     */
    public void send(T data) {
        MessageEntity message = this.saveMessage2DB(data);
        this.addMessage2Cache(message);
        this.hincrexUnReadCount(message.getToUserId(), message.getCategory());
    }

    /**
     * 批量读取消息
     *
     * @param userId
     * @param category
     */
    public void batchReadMessage(String userId, String category) {
        messageDAO.updateUserMessageReadState(userId, category);
        this.updateMessageStateCache(userId, category);
    }

    /**
     * 单条读取消息
     *
     * @param userId
     * @param messageId
     */
    public void readMessage(String userId, Long messageId) {
        MessageEntity message = messageDAO.findById(messageId).orElse(null);
        Assert.isTrue(message != null, "消息不存在");
        Assert.isTrue(message.getToUserId().equals(userId), "无读取权限");
        message.setState(true);
        messageDAO.save(message);
        this.hdecrexUnReadCount(userId, message.getCategory());
    }

    /**
     * 获取最新的几条消息
     *
     * @param currentUserId
     * @param category
     * @param size
     * @return
     */
    public List<MessageResponseVO> listMessage(String currentUserId, String category, int size) {
        String key = createUserMessageListCacheKey(currentUserId, category);
        long total = redisUtil.zSetSize(key);
        if (total == 0) {
            return Collections.emptyList();
        }
        Set<String> messageCacheSet = redisUtil.zreverseRange(key, 0, size);
        List<MessageEntity> messages = messageCacheSet.stream().map(cache -> {
            return JsonUtil.fromJson(cache, MessageEntity.class);
        }).collect(Collectors.toList());
        return convertMessageResponseVoList(messages);
    }

    /**
     * 消息翻页查询
     *
     * @return
     */
    public PAIPageResponseBeanUtil<List<MessageResponseVO>> pageMessage(String currentUserId,
                                                                        String category,
                                                                        int pageNo,
                                                                        int pageSize) {
        // 消息列表redis Key
        String key = this.createUserMessageListCacheKey(currentUserId, category);
        if (redisUtil.exists(key)) {
            this.refreshUserMessageCache(currentUserId, category);
        }
        // redis查询消息总数
        long total = redisUtil.zSetSize(key);
        if (total == 0) {
            return PAIPageResponseBeanUtil.success(pageNo, pageSize, 0, Collections.emptyList());
        }
        if (pageNo == 1) {
            // 批量读取全部消息
            this.batchReadMessage(currentUserId, category);
        }
        // 基于Zset分页查询消息索引列表
        int start = (pageNo - 1) * pageSize;
        int end = pageNo * pageSize;
        Set<String> messageCacheSet = redisUtil.zreverseRange(key, start, end);
        // 批量查询redis获取消息详情
        List<MessageEntity> messages = this.batchGetMessageFromCache(new ArrayList<>(messageCacheSet));
        return PAIPageResponseBeanUtil.success(pageNo, pageSize, total, convertMessageResponseVoList(messages));
    }

    private void refreshUserMessageCache(String currentUserId,
                                         String category) {
        List<MessageEntity> messages = messageDAO.findAllByToUserIdAndCategoryOrderByIdAsc(currentUserId, category);
        if (CollectionUtils.isEmpty(messages)) {
            return;
        }
        String key = this.createUserMessageListCacheKey(currentUserId, category);
        Set<ZSetOperations.TypedTuple<String>> tuples = messages.stream().map(
                message -> {
                    String value = String.valueOf(message.getId());
                    Double score = Double.valueOf(message.getId());
                    return new DefaultTypedTuple<>(value, score);
                }).collect(Collectors.toSet());
        redisUtil.zadd(key, tuples, RedisKeyEnum.MESSAGE_LIST_CACHE.getExpireTime());
    }

    /**
     * 消息转换
     *
     * @param messageEntities
     * @return
     */
    private List<MessageResponseVO> convertMessageResponseVoList(List<MessageEntity> messageEntities) {
        if (CollectionUtils.isEmpty(messageEntities)) {
            return Collections.emptyList();
        }
        /**
         * 查询消息发送人信息
         */
        Map<String, UserBaseResponseInfoVO> userMap = this.getMessageFromUserInfoMap(messageEntities);
        /**
         * 查询消息中涉及到的资源信息
         */
        Map<String, Map<String, MessageResourceVO>> resourceMap = this.getMessageResourceInfoMap(messageEntities);

        List<MessageResponseVO> messageResponseVOList = new ArrayList<>();

        for (MessageEntity message : messageEntities) {
            MessageResponseVO messageResponse = new MessageResponseVO();
            BeanUtils.copyProperties(message, messageResponse);
            messageResponse.setCategoryName(MessageConstants.getCategoryName(message.getCategory()));
            messageResponse.setType(message.getType());
            messageResponse.setTypeName(MessageConstants.getTypeName(message.getType()));
            messageResponse.setTime(message.getCreateDate().getTime());
            messageResponse.setFromUser(userMap.get(message.getFromUserId()));
            messageResponse.setToUser(userMap.get(message.getToUserId()));

            MessageResourceVO resource = null;
            String resourceType = message.getResourceType();
            String resourceId = message.getResourceId();
            if (ResourceTypeEnum.ARTICLE.getType().equals(resourceType) && StringUtils.isNotBlank(resourceId)) {
                resource = resourceMap.get(ResourceTypeEnum.ARTICLE.getType()).get(resourceId);
            }
            if (ResourceTypeEnum.BBS.getType().equals(resourceType) && StringUtils.isNotBlank(resourceId)) {
                resource = resourceMap.get(ResourceTypeEnum.BBS.getType()).get(resourceId);
            }
            if (ResourceTypeEnum.CODELINK.getType().equals(resourceType) && StringUtils.isNotBlank(resourceId)) {
                resource = resourceMap.get(ResourceTypeEnum.CODELINK.getType()).get(resourceId);
            }
            if (ResourceTypeEnum.RESOURCE.getType().equals(resourceType) && StringUtils.isNotBlank(resourceId)) {
                resource = resourceMap.get(ResourceTypeEnum.RESOURCE.getType()).get(resourceId);
            }
            if (ResourceTypeEnum.CTC_QUESTION.getType().equals(resourceType) && StringUtils.isNotBlank(resourceId)) {
                resource = resourceMap.get(ResourceTypeEnum.CTC_QUESTION.getType()).get(resourceId);
            }
            if (ResourceTypeEnum.CTC_EXPERIENCE.getType().equals(resourceType) && StringUtils.isNotBlank(resourceId)) {
                resource = resourceMap.get(ResourceTypeEnum.CTC_EXPERIENCE.getType()).get(resourceId);
            }
            if (ResourceTypeEnum.CTC_VIDEO.getType().equals(resourceType) && StringUtils.isNotBlank(resourceId)) {
                resource = resourceMap.get(ResourceTypeEnum.CTC_VIDEO.getType()).get(resourceId);
            }
            if (resource != null) {
                resource.setTitle(HtmlUtil.getTxtContent(resource.getTitle()));
            }
            messageResponse.setResource(resource);
            messageResponseVOList.add(messageResponse);
        }
        return messageResponseVOList;
    }

    /**
     * 批量查询消息发送人用户详情
     *
     * @param messageEntities
     * @return
     */
    private Map<String, UserBaseResponseInfoVO> getMessageFromUserInfoMap(List<MessageEntity> messageEntities) {
        if (CollectionUtils.isEmpty(messageEntities)) {
            return Collections.emptyMap();
        }
        Set<String> userIds = messageEntities.stream().map(MessageEntity::getFromUserId).collect(Collectors.toSet());
        Map<String, UserBaseResponseInfoVO> userMap = userServiceClient.batchGetUserInfoFromCache(new ArrayList<>(userIds));
        userMap.put(MessageConstants.FROM_SYSTEM, userServiceClient.getSystemUserInfo());
        return userMap;
    }

    /**
     * 查询消息中涉及到的资源信息
     *
     * @param messageEntities
     * @return
     */
    private Map<String, Map<String, MessageResourceVO>> getMessageResourceInfoMap(List<MessageEntity> messageEntities) {
        Map<String, Map<String, MessageResourceVO>> resourceMap = new HashMap<>();
        List<String> articleIdList = new ArrayList<>();
        List<String> resourceIdList = new ArrayList<>();
        List<String> codeIdList = new ArrayList<>();
        List<String> momentIdList = new ArrayList<>();
        List<String> qestionIdList = new ArrayList<>();
        List<String> expIdList = new ArrayList<>();
        List<String> videoIdList = new ArrayList<>();
        for (MessageEntity messageEntity : messageEntities) {
            if (StringUtils.isBlank(messageEntity.getResourceType())) {
                continue;
            }
            switch (messageEntity.getResourceType())
            {
                case "ARTICLE":
                    articleIdList.add(messageEntity.getResourceId());
                    break;
                case "BBS":
                    momentIdList.add(messageEntity.getResourceId());
                    break;
                case "RESOURCE":
                    resourceIdList.add(messageEntity.getResourceId());
                    break;
                case "CODELINK":
                    codeIdList.add(messageEntity.getResourceId());
                    break;
                case "CTC_QUESTION":
                    qestionIdList.add(messageEntity.getResourceId());
                    break;
                case "CTC_EXPERIENCE":
                    expIdList.add(messageEntity.getResourceId());
                    break;
                case "CTC_VIDEO":
                    videoIdList.add(messageEntity.getResourceId());
                    break;
            }
        }
        if (CollectionUtils.isNotEmpty(resourceIdList)) {
            resourceMap.put("RESOURCE", resourceService.batchGetMessResource(resourceIdList));
        }
        if (CollectionUtils.isNotEmpty(qestionIdList)) {
            resourceMap.put("CTC_QUESTION", questionService.batchGetMessResource(qestionIdList));
        }
        if (CollectionUtils.isNotEmpty(expIdList)) {
            resourceMap.put("CTC_EXPERIENCE", experienceService.batchGetMessResource(expIdList));
        }
        if (CollectionUtils.isNotEmpty(videoIdList)) {
            resourceMap.put("CTC_VIDEO", videoService.batchGetMessResource(videoIdList));
        }
        return resourceMap;
    }

    /**
     * 查询用户未读消息数量
     * @param userId
     * @return
     */
    public UserUnReadMessageCountVO getUserUnReadMessageCountFromCache(String userId) {
        UserUnReadMessageCountVO unReadMessageCountVO = new UserUnReadMessageCountVO();
        unReadMessageCountVO.setUnReadLikeMessageCount(getUnReadMessageCountFromCache(userId, MessageConstants.CategoryEnum.LIKE_COLLECT));
        unReadMessageCountVO.setUnReadCommentMessageCount(getUnReadMessageCountFromCache(userId, MessageConstants.CategoryEnum.COMMENT_REPLY));
        unReadMessageCountVO.setUnReadFocusMessageCount(getUnReadMessageCountFromCache(userId, MessageConstants.CategoryEnum.FOCUS));
        unReadMessageCountVO.setUnReadSystemMessageCount(getUnReadMessageCountFromCache(userId, MessageConstants.CategoryEnum.SYSTEM));
        return unReadMessageCountVO;
    }

    private Long getUnReadMessageCountFromCache(String userId, MessageConstants.CategoryEnum categoryEnum) {
        String unReadKey = this.createUserUnReadMessageCountCacheKey(categoryEnum.getCategory());
        String cache = redisUtil.hget(unReadKey, userId);
        return StringUtils.isNotBlank(cache) ? Long.valueOf(cache) : 0L;
    }

    /**
     * 消息入库
     *
     * @param data
     * @return
     */
    private MessageEntity saveMessage2DB(T data) {
        MessageEntity message = new MessageEntity();
        BeanUtils.copyProperties(data, message);
        message.setState(false);
        message.setCreateDate(new Date());
        return messageDAO.save(message);
    }

    /**
     * 消息加入redis缓存
     *
     * @param message
     */
    private void addMessage2Cache(MessageEntity message) {
        String messageListKey = this.createUserMessageListCacheKey(message.getToUserId(), message.getCategory());
        String messageDetailKey = this.createUserMessageDetailCacheKey(message.getId());
        redisUtil.set(messageDetailKey, JsonUtil.toJsonString(message), RedisKeyEnum.MESSAGE_DETAIL_CACHE.getExpireTime());
        redisUtil.zadd(messageListKey, message.getId().toString(), message.getCreateDate().getTime(), RedisKeyEnum.MESSAGE_LIST_CACHE.getExpireTime());
    }

    private List<MessageEntity> batchGetMessageFromCache(List<String> messageIndexSet) {
        if (CollectionUtils.isEmpty(messageIndexSet)) {
            return Collections.emptyList();
        }
        List<String> messageKeys = messageIndexSet.stream().map(mid ->
                createUserMessageDetailCacheKey(Long.valueOf(mid))).collect(Collectors.toList());
        // 批量查询redis，获取消息详情
        List<String> caches = redisUtil.mget(messageKeys);
        List<MessageEntity> messageEntities = new ArrayList<>();
        for (int i = 0; i < messageIndexSet.size(); i++) {
            String messageId = messageIndexSet.get(i);
            Long mid = Long.valueOf(messageId);
            String cache = caches.get(i);
            if (StringUtils.isNotBlank(cache)) {
                messageEntities.add(JsonUtil.fromJson(cache, MessageEntity.class));
            } else {
                MessageEntity messageEntity = messageDAO.findById(mid).orElse(null);
                if (messageEntity != null) {
                    messageEntities.add(messageEntity);
                    String key = createUserMessageDetailCacheKey(mid);
                    redisUtil.set(key, JsonUtil.toJsonString(messageEntity), RedisKeyEnum.MESSAGE_DETAIL_CACHE.getExpireTime());
                }
            }
        }
        return messageEntities;
    }

    /**
     * 未读消息数原子+1
     *
     * @param category
     * @param toUserId
     */
    private void hincrexUnReadCount(String toUserId, String category) {
        String unReadKey = this.createUserUnReadMessageCountCacheKey(category);
        redisUtil.hincrex(unReadKey, toUserId);
    }

    /**
     * 未读消息数原子-1
     *
     * @param category
     * @param toUserId
     */
    private void hdecrexUnReadCount(String toUserId, String category) {
        String unReadKey = this.createUserUnReadMessageCountCacheKey(category);
        redisUtil.hdecrex(unReadKey, toUserId);
    }

    /**
     * 更新消息为已读状态
     *
     * @param toUserId
     * @param category
     */
    private void updateMessageStateCache(String toUserId, String category) {
        String unReadKey = this.createUserUnReadMessageCountCacheKey(category);
        redisUtil.hset(unReadKey, toUserId, "0");
    }

    /**
     * 消息列表缓存KEY
     *
     * @param userId
     * @param category
     * @return
     */
    private String createUserMessageListCacheKey(String userId, String category) {
        return RedisKeyEnum.MESSAGE_LIST_CACHE.getKey(category, userId);
    }

    /**
     * 消息详情缓存KEY
     *
     * @param messageId
     * @return
     */
    private String createUserMessageDetailCacheKey(Long messageId) {
        return RedisKeyEnum.MESSAGE_DETAIL_CACHE.getKey(messageId.toString());
    }

    /**
     * 消息列表缓存KEY
     *
     * @param category
     * @return
     */
    private String createUserUnReadMessageCountCacheKey(String category) {
        return RedisKeyEnum.MESSAGE_UNREAD_COUNT_CACHE.getKey(category);
    }
}
